Implementing a TRUE Microsoft common controls slider

level: intermediate-advanced

© 2004, Brad Moore

author website:

http://www.freewebs.com/lb-connection

Home

ActiveX DLLs 2

API Trackbar/Slider

OOP

Stylebits - Textboxes

Using a ListBox

Real 3-D

Context Sensitive Help

Program Design

Texteditors

Tip Corner - Nomainwin

API Corner - MainWin

Sprite Byte: Scoreboard

Eddie

Newsletter help

Index


The slider control...

Well I call it a slider control, Microsoft calls it a trackbar - here is how Microsoft introduces it:

Trackbars are useful when you want the user to select a discrete value or a set of consecutive values in a range. For example, you might use a trackbar to allow the user to set the repeat rate of the keyboard by moving the slider to a given tick mark. The following illustration shows a typical trackbar.

The slider in a trackbar moves in increments that you specify when you create it. The values in this range are referred to as logical units. For example, if you specify that the trackbar should have logical units that range from zero to five, the slider can occupy only six positions: a position at the left side of the trackbar and one position for each increment in the range. Typically, each of these positions is identified by a tick mark.

I have long wanted to use a simple slider control in Liberty Basic. I have been a bit disappointed that Liberty Basic does not offer this as a standard control. To compensate I have written several KLUDGES trying to assemble the various components and code to make a pseudo slider using native Liberty Basic code and graphics. The results have always been less than impressive.

I recently had a cause to integrate one of these kludge of a slider controls into a project I am working on. The result was not very satisfying, and it took a lot of work to adjust the code I was pasting in to be compatible with the existing code modules. To my horror I discovered that almost half of the finished program was code to control this nasty slider.

I decided that there must be a better way. I started out with the concept of implementing a slider wrapper in another language for the Microsoft common controls slider and presenting it to LB as a DLL. What was exciting, as I looked into the control a bit closer, I noticed it conformed very nicely to the same standards which checkbox, statictext, textbox and other common controls conform. I got to wondering if I could just skip the wrapping of the control altogether and just create the control from Liberty Basic.

The good news: This is quite possible. The secret was buried there on Microsoft's MSDN site [http://msdn.microsoft.com]. The CreateWindowsEx function is able to create one from the internal trackbar class. Liberty Basic seems to accept this on a form without issue.

But after the control is created, the question of interface still looms. I found from the MSDN site that the SendMessage function is used to interface with the control. There were still some big issues. The documentation on the MSDN site assumes you are using Microsoft tools with the predefined constants for message type. Liberty Basic does not have these constants predefined. I needed to find a definition.

A search of the internet (thanks Google! [http://www.google.com]) led me to some documentation on the Eiffel website. [http://docs.eiffel.com/eiffelstudio/libraries/wel/reference/consts/wel_tbm_constants_chart.html]

This is a condensed list of the most useful constants for implementation in Liberty Basic including a brief description from the MSDN site:

TBM_CLEARTICS: INTEGER 1033

Removes the current tick marks from a trackbar. This message does not remove the first and last tick marks, which are created automatically by the trackbar.


TBM_GETPOS: INTEGER 1024

Retrieves the current logical position of the slider in a trackbar. The logical positions are the integer values in the trackbar's range of minimum to maximum slider positions.


TBM_SETLINESIZE: INTEGER 1047

Sets the number of logical positions the trackbar's slider moves in response to keyboard input from the arrow keys. The logical positions are the integer increments in the trackbar's range of minimum to maximum slider positions.


TBM_SETPAGESIZE: INTEGER 1045

Sets the number of logical positions the trackbar's slider moves in response to keyboard input, such as the arrow keys, or mouse input, such as clicks in the trackbar's channel. The logical positions are the integer increments in the trackbar's range of minimum to maximum slider positions.


TBM_SETPOS: INTEGER 1029

Sets the current logical position of the slider in a trackbar.


TBM_SETRANGE: INTEGER 1030

Sets the range of minimum and maximum logical positions for the slider in a trackbar.


TBM_SETRANGEMAX: INTEGER 1032

Sets the maximum logical position for the slider in a trackbar.


TBM_SETRANGEMIN: INTEGER 1031

Sets the minimum logical position for the slider in a trackbar.


TBM_SETSEL: INTEGER 1034

Sets the starting and ending positions for the available selection range in a trackbar.


TBM_SETSELEND: INTEGER 1036

Sets the ending logical position of the current selection range in a trackbar. This message is ignored if the trackbar does not have the TBS_ENABLESELRANGE style.


TBM_SETSELSTART: INTEGER 1035

Sets the starting logical position of the current selection range in a trackbar. This message is ignored if the trackbar does not have the TBS_ENABLESELRANGE style.


TBM_SETTIC: INTEGER 1028

Sets a tick mark in a trackbar at the specified logical position.

Each of these (along with many others) are fully explained on the following MSDN website: [http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/trackbar/reflist.asp. As I noted earlier, I have concentrated on just a few of the possible messages used to control the slider. You may wish to do other unique things which will require some experimentation and study to implement.

Stylebits that can be applied to the control at the time of creation - again I found the integer equivalent values of many of these styles at the Eiffel website - [http://docs.eiffel.com/eiffelstudio/libraries/wel/reference/consts/]:

TBS_AUTOTICKS: INTEGER 1

The trackbar control has a tick mark for each increment in its range of values.


TBS_BOTH: INTEGER 8

The trackbar control displays tick marks on both sides of the control. This will be both top and bottom when used with TBS_HORZ or both left and right if used with TBS_VERT.


TBS_BOTTOM: INTEGER 0

The trackbar control displays tick marks below the control. This style is valid only with TBS_HORZ.


TBS_DOWNISLEFT: INTEGER this one was not available

By default, the trackbar control uses down equal to right and up equal to left. Use the TBS_DOWNISLEFT style to reverse the default, making down equal left and up equal right.


TBS_ENABLESELRANGE: INTEGER 32

The trackbar control displays a selection range only. The tick marks at the starting and ending positions of a selection range are displayed as triangles (instead of vertical dashes), and the selection range is highlighted.


TBS_FIXEDLENGTH: INTEGER 64

The trackbar control allows the size of the slider to be changed with the TBM_SETTHUMBLENGTH message.


TBS_HORZ: INTEGER 0

The trackbar control is oriented horizontally. This is the default orientation.


TBS_LEFT: INTEGER 4

The trackbar control displays tick marks to the left of the control. This style is valid only with TBS_VERT.


TBS_NOTHUMB: INTEGER 128

The trackbar control does not display a slider.


TBS_NOTICKS: INTEGER 16

The trackbar control does not display any tick marks.


TBS_RIGHT: INTEGER 0

The trackbar control displays tick marks to the right of the control. This style is valid only with TBS_VERT.


TBS_TOOLTIPS: INTEGER 256

Version 4.70. The trackbar control supports ToolTips. When a trackbar control is created using this style, it automatically creates a default ToolTip control that displays the slider's current position. You can change where the ToolTips are displayed by using the TBM_SETTIPSIDE message.


TBS_TOP: INTEGER 4

The trackbar control displays tick marks above the control. This style is valid only with TBS_HORZ.


TBS_VERT: INTEGER 2

The trackbar control is oriented vertically.

A slider (or Trackbar) is created using the CreateWindowEx function in the Windows API. Lets take a look at the ugly call:

HWND CreateWindowEx(          
    DWORD dwExStyle,
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HINSTANCE hInstance,
    LPVOID lpParam
);

dwExStyle: [in] Specifies the extended window style of the window being created.

lpClassName: [in] Pointer to a null-terminated string

lpWindowName: [in] Pointer to a null-terminated string that specifies the window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title bar.

dwStyle: [in] Specifies the style of the window being created.

x: [in] Specifies the initial horizontal position of the window.

y: [in] Specifies the initial vertical position of the window.

nWidth: [in] Specifies the width, in device units, of the window.

nHeight: [in] Specifies the height, in device units, of the window.

hWndParent: [in] Handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid window handle.

hMenu: [in] Handle to a menu, or specifies a child-window identifier, depending on the window style.

hInstance: [in] Windows 95/98/Me: Handle to the instance of the module to be associated with the window. Windows NT/2000/XP: This value is ignored.

lpParam: [in] Pointer to a value to be passed to the window through the CREATESTRUCT structure passed in the lpParam parameter the WM_CREATE message. If an application calls CreateWindow to create a MDI client window, lpParam must point to a CLIENTCREATESTRUCT structure.

Before this function can be called to create the control, you must initialize the common controls interface with the following call:

InitCommonControls(); // loads common control's DLL

Thankfully Alyce Watson has already encapsulated these calls to create similar windows (along with another call I found I needed - GetWindowLong, which is used to subclass a control or window) in her very wonderful and extremely powerful editor Liberty Basic Workshop. I was able to leverage the similar constructed calls to create this new function called CreateSlider:


Function CreateSlider(hW, x, y, w, h)
    CallDLL #comctl32, "InitCommonControls", re As Void
    style = _WS_VISIBLE or _WS_CHILD
    hInst=GetWindowLong(hW, _GWL_HINSTANCE)
    CallDLL #user32, "CreateWindowExA",0 As Long,"msctls_trackbar32" As Ptr,_
        "" As Ptr, style As Long,x As Long,y As Long,w As Long,h As Long,_
        hW As Long, 0 As Long, hInst As Long, 0 As Long,CreateSlider As Long
    End Function

This slider control does not provide many flashy options, but you can alter the styles in the third parameter if the CreateWindowExA call and do some fun stuff. As an additional improvement, you might change the function definition to support passing the styles into the function as a calling argument.

Call this function after the window is open and place the control on the form in the x and y locations using the form coordinante system. The slider will be sized according to w and h. A height of 20 or more is required to display the slider.

Don't forget you will need to add the GetWindowLong function to your program for the code above to actually work:


Function GetWindowLong(hW, type)
    CallDLL #user32, "GetWindowLongA",hW As Long, type As Long,GetWindowLong As Long
    End Function

After the slider is created, use the following function to send various messages to it. I altered the standard SendMessage Sub Program that Alyce has included in Liberty Basic Workshop, turning it into a function. In most cases, this does not make much sense. Sending messages rarely returns a useful value. In the case of the Slider the slide value is however returned via the message GetPos. Here is the SendMessage function:


Function SendMessageLong(hWnd,msg,w,l)
    CallDLL #user32, "SendMessageA", hWnd As Long, _
    msg As Long, w As Long, l As Long, re As Long
    SendMessageLong = re
    End function           

To use all this, include the functions above in your code. The following code will set some general parameters for a typical slider and cause it to be displayed on a window:


    hW = hWnd(#main)
    SlidehW = CreateSlider(hW, 20, 20, 260, 30)
    
    'set the upper range (TBM_SETRANGEMAX = 1032)
    stat = SendMessageLong(SlidehW,1032,True,500)
    'set the lower range (TBM_SETRANGEMIN = 1031)
    stat = SendMessageLong(SlidehW,1031,True,1)
    
    for x = 50 to 500 step 49
        'set tics (TBM_SETTIC = 1028)
        stat = SendMessageLong(SlidehW,1028,True,x)
    next x
    
    'set pagesize (how far a click will move the thumb (TBM_SETPAGESIZE = 1045)
    stat = SendMessageLong(SlidehW,1045,True,49)
    
    'set line size (TBM_SETpos = 1029)
    stat = SendMessageLong(SlidehW,1029,True,98)

In order to detect the changes in value which the slider may be indicating, I created a timer and a routine to scan for the current value:


Timer [checkSlide], 250

[checkSlide]
    'get the value the slider is setting on (TBM_GETPOS = 1024)
    value = SendMessageLong(SlidehW,1024,0,0)
    #main.value, "Current Value: ";value
    wait

This works, but appears not to be the only way to do things. According to the documentation, the control will also send messages that can be intercepted and acted on. Liberty Basic is not able to do this directly, but by using Brent Thorn's WMLiberty subclassing DLL you should be able to capture these messages and avoid using a time to check for updates. This is also good if you are already using WMLiberty in an existing application where you want a slider, as WMLiberty and Timers can not coexist for the same application. I did not research this element of the interface too deeply. If you are interested, these are the messages to watch for:

Trackbar Notification Messages

A trackbar notifies its parent window of user actions by sending the parent a WM_HSCROLL or WM_VSCROLL message. A trackbar with the TBS_HORZ style sends WM_HSCROLL messages. A trackbar with the TBS_VERT style sends WM_VSCROLL messages. The low-order word of the wParam parameter of WM_HSCROLL or WM_VSCROLL contains the notification code. For the TB_THUMBPOSITION and TB_THUMBTRACK notifications, the high-order word of the wParam parameter specifies the position of the slider. For all other notifications, the high-order word is zero; send the TBM_GETPOS message to determine the slider position. The lParam parameter is the handle to the trackbar.

The system sends the TB_BOTTOM, TB_LINEDOWN, TB_LINEUP, and TB_TOP notification messages only when the user interacts with a trackbar by using the keyboard. The TB_THUMBPOSITION and TB_THUMBTRACK notification messages are only sent when the user is using the mouse. The TB_ENDTRACK, TB_PAGEDOWN, and TB_PAGEUP notification messages are sent in both cases. The following table lists the trackbar notification messages and the events (virtual key codes or mouse events) that cause the notifications to be sent.

(Message: Reason)

TB_BOTTOM: VK_END

TB_ENDTRACK: WM_KEYUP(the user released a key that sent a relevant virtual key code)

TB_LINEDOWN: VK_RIGHT or VK_DOWN

TB_LINEUP: VK_LEFT or VK_UP

TB_PAGEDOWN: VK_NEXT (the user clicked the channel below or to the right of the slider)

TB_PAGEUP: VK_PRIOR (the user clicked the channel above or to the left of the slider)

TB_THUMBPOSITION: WM_LBUTTONUP following a TB_THUMBTRACK notification message

TB_THUMBTRACK: Slider movement (the user dragged the slider)

TB_TOP: VK_HOME

In addition to this, MSDN notes that Trackbar is capable to responding to several messages that are sent to it. I have not used any of these, but some look useful, and even a couple desirable, such as WM_DESTROY.

Default Trackbar Message Processing

This section describes the window message processing performed by a trackbar.

WM_CAPTURECHANGED: Kills the timer if one was set during WM_LBUTTONDOWN processing and sends the TB_THUMBPOSITION notification message, if necessary. It always sends the TB_ENDTRACK notification message.

WM_CREATE: Performs additional initialization, such as setting the line size, page size, and tick mark frequency to default values.

WM_DESTROY: Frees resources.

WM_ENABLE: Repaints the trackbar window.

WM_ERASEBKGND: Erases the window background, using the current background color for the trackbar.

WM_GETDLGCODE: Returns the DLGC_WANTARROWS value.

WM_KEYDOWN: Processes the direction keys and sends the TB_TOP, TB_BOTTOM, TB_PAGEUP, TB_PAGEDOWN, TB_LINEUP, and TB_LINEDOWN notification messages, as appropriate.

WM_KEYUP: Sends the TB_ENDTRACK notification message if the key was one of the direction keys.

WM_KILLFOCUS: Repaints the trackbar window.

WM_LBUTTONDOWN: Sets the focus and the mouse capture to the trackbar. When necessary, it sets a timer that determines how quickly the slider moves toward the mouse cursor when the user holds down the mouse button in the window.

WM_LBUTTONUP: Releases the mouse capture and kills the timer if one was set during WM_LBUTTONDOWN processing. It sends the TB_THUMBPOSITION notification message, if necessary. It always sends the TB_ENDTRACK notification message.

WM_MOUSEMOVE: Moves the slider and sends the TB_THUMBTRACK notification message when tracking the mouse (see WM_TIMER).

WM_PAINT: Paints the trackbar. If the wParam parameter is non-NULL, the control assumes that the value is an HDC and paints using that device context.

WM_SETFOCUS: Repaints the trackbar window.

WM_SIZE: Sets the dimensions of the trackbar, removing the slider if there is not enough room to display it.

WM_TIMER: Retrieves the mouse position and updates the position of the slider. (It is received only when the user is dragging the slider.)

WM_WININICHANGE: Initializes slider dimensions.

DEMO

Finally, I would like to end this article with a nice little Liberty Basic Demo showing a slider (or trackbar) in use.

Editor's note: See slider.bas which is included in the zipped archive of this issue.


Home

ActiveX DLLs 2

API Trackbar/Slider

OOP

Stylebits - Textboxes

Using a ListBox

Real 3-D

Context Sensitive Help

Program Design

Texteditors

Tip Corner - Nomainwin

API Corner - MainWin

Sprite Byte: Scoreboard

Eddie

Newsletter help

Index